Tidy eval with dynamic filters
January 04, 2023
Tidyverse with its many packages has certainly made life easier for a lot of Data scientists.
I had a peculiar problem which led me down in to a rabbit hole of tidyeval , the bang-bang operator all of which was new to me. Maybe this is helpful for someone.
Suppose you have a dataframe (or a tibble) and there are multiple plots( in this case echarts from echarts4r package) which could be used to click . We would like to display a filtered table based on which selections were made. Essentially, the filter portion of the code is dynamic
for example dataframe1 |> filter(condition1, condition2,..) and conditions are of the form col1 == someval etc
The way to do it is to create expressions as strings and then evaluate them later. pls refer tidyeval guide or rlang
First we create a list of the chart click inputs so that we can get data ( this entire code will be wrapped in a reactive later)
temp1 <- setnames(list(input$chart1_clicked_data, input$chart2_clicked_data),list("chart1_variable","chart2variable"))
Now if the chart is left unclicked, the corresponding input is NULL, so we remove that in the next step
temp1 <- temp1[lengths(temp1) != 0]
Now we need to somehow use both the chart1_variable as the right side of the filter expression and the values inside the inputs on the right side.
using imap from purrr
temp3 <- imap(temp1, function(x,y) {
expr(!!sym(y)) == !!x$Value)
}
Here, y is the name of the list element and x holds the values as returned. !! causes it to be evaluated inside the expr(). We use !!sym(y) because we want the unquoted value in the filter expression i.e. chart1variable == “xx” not “chart1variable” == “xx”
temp3 now holds a list of expressions of the form [“chart1_variable == 1, ..] , but it’s a named list so we remove names in the next step.
names(temp3) <- NULL
we can now evaluate all the expressions at once.
eval(dataframe1 |> filter(!!!temp3))
The !!! parses the list correctly
This entire code is to be inside a reactive function so that it looks like this
data_filtered <- reactive({
temp1 <- setnames(list(input$chart1_clicked_data, input$chart2_clicked_data),list("chart1_variable","chart2variable"))
temp1 <- temp1[lengths(temp1) != 0]
temp3 <- imap(temp1, function(x,y) {
expr(!!sym(y)) == !!x$Value
}
names(temp3) <- NULL
eval(dataframe1 |> filter(!!!temp3))
})
Now we could add conditionals to check for length of temp1 so that no filtering happens if nothing is clicked etc.